// 用户接口

package api

import (
	"api/common"
	"api/common/logger"
	"api/common/orm"
	"api/common/web"
	"api/models"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/objcoding/wxpay"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"
)

// @TagName 用户接口
// @Controller UserController
type UserController struct{}

// @Title 微信用户登录
// @Route /user/login --> Login
// @Param {"code":"","nickName":"","userLogo":"","address":""}
// @Method post
func (this *UserController) Login(w http.ResponseWriter, r *http.Request) {
	defer web.PanicHandler(w, r)
	inputJson, msg := web.ExemptTokenCheckRequest(r)
	if msg != nil {
		web.ServeJSON(w, msg)
		return
	}

	code := inputJson["code"]
	nickName := inputJson["nickName"]
	userLogo := inputJson["userLogo"]
	address := inputJson["address"]

	if code == nil || code == "" {
		web.ServeJSON(w, web.JsonMsg{false, "用户登录凭证code不能为空", 1})
		return
	}

	url := "https://api.weixin.qq.com/sns/jscode2session" +
		"?grant_type=authorization_code" +
		"&js_code=" + common.ToStr(code) +
		"&appid=" + common.Config.WX.AppID +
		"&secret=" + common.Config.WX.AppSecret
	req, err := http.NewRequest("get", url, nil)
	if err != nil {
		logger.Error("获取微信用户的openid发生错误 http.NewRequest", err)
		web.ServeJSON(w, web.JsonMsg{false, "获取微信用户的openid发生错误" + err.Error(), 1})
		return
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		logger.Error("获取微信用户的openid发生错误 client.Do", err)
		web.ServeJSON(w, web.JsonMsg{false, "获取微信用户的openid发生错误" + err.Error(), 1})
		return
	}

	if resp.StatusCode != 200 {
		web.ServeJSON(w, web.JsonMsg{false, "获取微信用户的openid发生错误" + common.ToStr(resp.StatusCode), 1})
		return
	}

	bodyByte, err := ioutil.ReadAll(resp.Body)
	_ = resp.Body.Close()
	if err != nil {
		logger.Error("获取微信用户的openid发生错误 ioutil.ReadAll", err)
		web.ServeJSON(w, web.JsonMsg{false, "获取微信用户的openid发生错误" + err.Error(), 1})
		return
	}

	inputJson = map[string]interface{}{}
	err = json.Unmarshal(bodyByte, &inputJson)
	if err != nil {
		logger.Error("获取微信用户的openid发生错误 json.Unmarshal", err)
		web.ServeJSON(w, web.JsonMsg{false, "获取微信用户的openid发生错误" + err.Error(), 1})
		return
	}

	errMsg := inputJson["errmsg"]
	if errMsg != nil {
		web.ServeJSON(w, web.JsonMsg{false, "获取微信用户的openid发生错误" + common.ToStr(errMsg), 1})
		return
	}

	openID := inputJson["openid"]
	sessionKey := inputJson["session_key"]

	sql := "select count(1) from student_info where weixin_id=?"
	count := 0
	conn := orm.NewOrm()
	err = conn.Raw(sql, openID).QueryRow(&count)
	if err != nil {
		logger.Error("查询注册用户发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "查询注册用户发生错误" + err.Error(), 1})
		return
	}

	message := ""
	user := &models.Info{}

	if count == 0 {
		message = "注册"
	} else {
		message = "登录"
		sql = "select * from student_info where weixin_id=?"
		err = conn.Raw(sql, openID).QueryRow(&user)
		if err != nil {
			logger.Error("查询用户信息发生错误", err)
			web.ServeJSON(w, web.JsonMsg{false, "查询用户信息发生错误" + err.Error(), 1})
			return
		}
	}

	if nickName != nil && nickName != "" {
		user.NickName = common.ToStr(nickName)
	}
	if userLogo != nil && userLogo != "" {
		user.UserLogo = common.ToStr(userLogo)
	}
	if address != nil && address != "" && user.Address == "" {
		user.Address = common.ToStr(address)
	}

	_ = conn.Begin()

	if count == 0 {
		user.WeixinId = common.ToStr(openID)
		user.Lodging = true  // 考虑到学校并不在市中心，所以让学生注册时默认住校
		user.Citytown = true // 默认是城市户口
		_, err = conn.Insert(user)
	} else {
		_, err = conn.Update(user)
	}

	if err != nil {
		_ = conn.Rollback()
		logger.Error("存储用户信息发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "存储用户信息发生错误" + err.Error(), 1})
		return
	}

	rec := &models.OperateRecord{
		StudentId: user.Id,
		Remark:    message,
	}
	_, err = conn.Insert(rec)
	if err != nil {
		_ = conn.Rollback()
		logger.Error("操作记录入库发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "操作记录入库发生错误" + err.Error(), 1})
		return
	}
	_ = conn.Commit()

	key := fmt.Sprintf("app_user_%d", user.Id)
	token := common.ToStr(sessionKey)
	common.RedisClient.Set(key, token, 0)

	result := map[string]interface{}{}
	result["status"] = 0
	result["success"] = true
	result["msg"] = message + "成功"
	result["user"] = user
	result["token"] = token
	web.ServeJSON(w, result)
}

// @Title 获取用户个人信息
// @Route /user/info --> Info
// @Param {"userId":0, "token":""}
// @Method post
func (this *UserController) Info(w http.ResponseWriter, r *http.Request) {
	defer web.PanicHandler(w, r)
	inputJson, msg := web.TokenCheckRequest(r)
	if msg != nil {
		web.ServeJSON(w, msg)
		return
	}

	userId := inputJson["userId"]
	sql := "select * from student_info where id=?"
	user := &models.Info{}
	conn := orm.NewOrm()
	err := conn.Raw(sql, userId).QueryRow(user)
	if err != nil {
		if strings.Index(err.Error(), "no row found") == -1 {
			logger.Info("查询用户信息发生错误", err)
			web.ServeJSON(w, web.JsonMsg{false, "查询用户信息发生错误" + err.Error(), 1})
			return
		}

		web.ServeJSON(w, web.JsonMsg{false, "用户信息不存在", 1})
		return
	}

	result := map[string]interface{}{}
	result["status"] = 0
	result["success"] = true
	result["msg"] = "获取用户信息成功"
	result["user"] = user
	web.ServeJSON(w, result)
}

// @Title 提交用户报名信息
// @Route /user/submit --> Submit
// @Param {"userId":0, "token":"", "data":{}}
// @Method post
func (this *UserController) Submit(w http.ResponseWriter, r *http.Request) {
	defer web.PanicHandler(w, r)
	inputJson, msg := web.TokenCheckRequest(r)
	if msg != nil {
		web.ServeJSON(w, msg)
		return
	}

	// 判断今天是否处于报名窗口期
	value := ""
	sql := "select `value` from basic_global_set where `name` = ?"
	conn := orm.NewOrm()
	err := conn.Raw(sql, "signUpTimeFrame").QueryRow(&value)
	if err != nil {
		logger.Error(err)
		web.ServeJSON(w, web.JsonMsg{false, err.Error(), 1})
		return
	}

	if len(value) == 11 {
		// 07-01,08-31
		dateFmt := "2006-01-02"
		now := time.Now()
		year := orm.ToStr(now.Year())
		valueData := strings.Split(value, ",")
		if len(valueData) == 2 {
			beginDate := year + "-" + valueData[0]
			endDate := year + "-" + valueData[1]
			d0, _ := time.Parse(dateFmt, beginDate)
			d1, _ := time.Parse(dateFmt, endDate)
			isBetween := now.After(d0) && now.Before(d1)
			if !isBetween {
				errMsg := "报名日期是从%d月%d日到%d月%d日，请知悉。"
				errMsg = fmt.Sprintf(errMsg, d0.Month(), d0.Day(), d1.Month(), d1.Day())
				web.ServeJSON(w, web.JsonMsg{false, errMsg, 1})
				return
			}
		}
	}

	userId := inputJson["userId"]
	data := inputJson["data"]
	if data == nil {
		web.ServeJSON(w, web.JsonMsg{false, "报名信息不能为空", 1})
		return
	}

	modelMap := data.(map[string]interface{})
	user := &models.Info{}

	// 验证"报名信息"的有效性
	sql = "select * from student_info where id=?"
	err = conn.Raw(sql, userId).QueryRow(user)
	if err != nil {
		if strings.Index(err.Error(), "no row found") == -1 {
			logger.Info("验证用户信息发生错误", err)
			web.ServeJSON(w, web.JsonMsg{false, "验证用户信息发生错误" + err.Error(), 1})
			return
		}

		web.ServeJSON(w, web.JsonMsg{false, "用户信息不存在", 1})
		return
	}

	// 新增需求: 用户报名后在未通过审核前仍可继续修改并提交报名信息,
	// 因为用户的报名信息可能存在填写错误, 用户需要改正后重新报名
	if user.Status != 0 && user.Status != 1 && user.Status != 3 {
		//状态: 0已注册待报名, 1已报名待审核, 2审核通过待交费, 3审核被拒, 4已交费待核实, 5已核实待分配, 6已分配
		web.ServeJSON(w, web.JsonMsg{false, "用户状态不正确", 1})
		return
	}

	// 检查"操作记录"里是否有依据(是否有前一状态的记录)
	count := 0
	sql = "select count(1) from student_operate_record where" +
		" student_id=? and add_time<now() and" +
		" (`status`=0 and admin_id=0 and (remark='注册' or remark='登录')) or" +
		" (`status`=3 and admin_id>0 and remark!='')" +
		" order by add_time desc"
	err = conn.Raw(sql, userId).QueryRow(&count)
	if err != nil {
		logger.Error("查询操作记录数量发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "查询操作记录数量发生错误" + err.Error(), 1})
		return
	}
	if count == 0 {
		web.ServeJSON(w, web.JsonMsg{false, "“操作记录”表里没有前一状态的记录", 1})
		return
	}

	web.JsonToStruct(modelMap, user)

	if user.IdCardNum != "" {
		// 新需求：对于已退学的学生，不再接受该生再次报名
		sql = "select count(1) from student_info where `exit` and id_card_num = ?"
		err = conn.Raw(sql, user.IdCardNum).QueryRow(&count)
		if err != nil {
			logger.Error("查询已退学的学生发生错误", err)
			web.ServeJSON(w, web.JsonMsg{false, "查询已退学的学生发生错误" + err.Error(), 1})
			return
		}
		if count > 0 {
			web.ServeJSON(w, web.JsonMsg{false, "已退学的学生不可再报名", 1})
			return
		}
	}

	user.Id = uint32(userId.(float64))
	user.Status = 1 // 更新用户状态为: 已报名待审核
	user.SignupTime = time.Now()
	_ = conn.Begin()
	_, err = conn.Update(user)
	if err != nil {
		_ = conn.Rollback()
		logger.Error("更新记录发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "更新记录发生错误" + err.Error(), 1})
		return
	}

	rec := &models.OperateRecord{
		StudentId: user.Id,
		Status:    1, // 已报名待审核
	}
	_, err = conn.Insert(rec)
	if err != nil {
		_ = conn.Rollback()
		logger.Error("操作记录入库发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "操作记录入库发生错误" + err.Error(), 1})
		return
	}
	_ = conn.Commit()

	result := map[string]interface{}{}
	result["status"] = 0
	result["success"] = true
	result["msg"] = "提交报名信息成功"
	result["user"] = user
	web.ServeJSON(w, result)
}

// @Title 保存用户报名信息
// @Route /user/save --> Save
// @Param {"userId":0, "token":"", "data":{}}
// @Method post
func (this *UserController) Save(w http.ResponseWriter, r *http.Request) {
	defer web.PanicHandler(w, r)
	inputJson, msg := web.TokenCheckRequest(r)
	if msg != nil {
		web.ServeJSON(w, msg)
		return
	}

	userId := inputJson["userId"]
	data := inputJson["data"]
	if data == nil {
		web.ServeJSON(w, web.JsonMsg{false, "报名信息不能为空", 1})
		return
	}

	modelMap := data.(map[string]interface{})
	user := &models.Info{}

	// 验证"报名信息"的有效性
	sql := "select * from student_info where id=?"
	conn := orm.NewOrm()
	err := conn.Raw(sql, userId).QueryRow(user)
	if err != nil {
		if strings.Index(err.Error(), "no row found") == -1 {
			logger.Info("验证用户信息发生错误", err)
			web.ServeJSON(w, web.JsonMsg{false, "验证用户信息发生错误" + err.Error(), 1})
			return
		}

		web.ServeJSON(w, web.JsonMsg{false, "用户信息不存在", 1})
		return
	}

	// 新增需求: 用户报名后在未通过审核前仍可继续报名, 因为用户的报名信息可能存在填写错误, 用户需要改正后重新报名
	if user.Status != 0 && user.Status != 3 && user.Status != 1 {
		//状态: 0已注册待报名, 1已报名待审核, 2审核通过待交费, 3审核被拒, 4已交费待核实, 5已核实待分配, 6已分配
		web.ServeJSON(w, web.JsonMsg{false, "用户状态不正确", 1})
		return
	}

	// 检查"操作记录"里是否有依据(是否有前一状态的记录)
	count := 0
	sql = "select count(1) from student_operate_record where" +
		" student_id=? and add_time<now() and" +
		" (`status`=0 and admin_id=0 and (remark='注册' or remark='登录')) or" +
		" (`status`=3 and admin_id>0 and remark!='')" +
		" order by add_time desc"
	err = conn.Raw(sql, userId).QueryRow(&count)
	if err != nil {
		logger.Error("查询操作记录数量发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "查询操作记录数量发生错误" + err.Error(), 1})
		return
	}
	if count == 0 {
		web.ServeJSON(w, web.JsonMsg{false, "“操作记录”表里没有前一状态的记录", 1})
		return
	}

	userStatus := user.Status
	web.JsonToStruct(modelMap, user)
	user.Id = uint32(userId.(float64))
	user.Status = userStatus
	_, err = conn.Update(user)
	if err != nil {
		logger.Error("更新记录发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "更新记录发生错误" + err.Error(), 1})
		return
	}

	result := map[string]interface{}{}
	result["status"] = 0
	result["success"] = true
	result["msg"] = "保存报名信息成功"
	result["user"] = user
	web.ServeJSON(w, result)
}

// 用户交费
// /user/pay --> Pay
// {"userId":1,"token":"a","phone":"","fee":1000.0}
// post
func (this *UserController) Pay(w http.ResponseWriter, r *http.Request) {
	defer web.PanicHandler(w, r)
	inputJson, msg := web.TokenCheckRequest(r)
	if msg != nil {
		web.ServeJSON(w, msg)
		return
	}

	userId := inputJson["userId"]
	phone := inputJson["phone"]
	fee := inputJson["fee"]
	if phone == nil || phone == "" {
		web.ServeJSON(w, web.JsonMsg{false, "手机号不能为空", 1})
		return
	}
	if fee == nil {
		web.ServeJSON(w, web.JsonMsg{false, "支付费用不能为空", 1})
		return
	}

	model := &models.Info{}
	sql := "select * from student_info where id=? and phone=?"
	conn := orm.NewOrm()
	err := conn.Raw(sql, userId, phone).QueryRow(model)
	if err != nil {
		if strings.Index(err.Error(), "no row found") == -1 {
			logger.Info("验证用户信息发生错误", err)
			web.ServeJSON(w, web.JsonMsg{false, "验证用户信息发生错误" + err.Error(), 1})
			return
		}

		web.ServeJSON(w, web.JsonMsg{false, "请填写你注册时的手机号", 1})
		return
	}
	if model.Status != 2 {
		//状态: 0已注册待报名, 1已报名待审核, 2审核通过待交费, 3审核被拒, 4已交费待核实, 5已核实待分配, 6已分配
		web.ServeJSON(w, web.JsonMsg{false, "用户状态不正确", 1})
		return
	}

	// 检查"操作记录"里是否有依据(是否有前一状态的记录)
	count := 0
	sql = "select count(1) from student_operate_record where" +
		" student_id=? and add_time<now() and" +
		" `status`=2 and admin_id>0 and remark=''" +
		" order by add_time desc"
	err = conn.Raw(sql, userId).QueryRow(&count)
	if err != nil {
		logger.Error("查询操作记录数量发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "查询操作记录数量发生错误" + err.Error(), 1})
		return
	}
	if count == 0 {
		web.ServeJSON(w, web.JsonMsg{false, "“操作记录”表里没有前一状态的记录", 1})
		return
	}

	model.Id = uint32(userId.(float64))
	model.Status = 4 // 更新用户状态为: 已交费待核实
	model.Fee = float32(fee.(float64))
	_ = conn.Begin()
	_, err = conn.Update(model)
	if err != nil {
		_ = conn.Rollback()
		logger.Error("更新记录发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "更新记录发生错误" + err.Error(), 1})
		return
	}

	rec := &models.OperateRecord{
		StudentId: model.Id,
		Status:    4, // 已交费待核实
		Remark:    common.ToStr(fee),
	}
	_, err = conn.Insert(rec)
	if err != nil {
		_ = conn.Rollback()
		logger.Error("操作记录入库发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "操作记录入库发生错误" + err.Error(), 1})
		return
	}

	// 微信支付 开始-----------------------------------------------------------------
	// 创建支付账户
	account := wxpay.NewAccount(
		common.Config.WX.AppID,
		common.Config.WX.MchID,
		common.Config.WX.ApiKey,
		false,
	)

	// 新建微信支付客户端
	client := wxpay.NewClient(account)

	// 设置证书
	account.SetCertData(common.Config.WX.CertPath)

	// 更改签名类型
	client.SetSignType(common.Config.WX.SignType)

	// 统一下单
	rand.Seed(time.Now().UnixNano())
	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
	captcha := fmt.Sprintf("%6v", rnd.Int31n(1000000))
	tradeNum := common.GetNow(false) + captcha
	params := make(wxpay.Params)
	params.SetString("body", "test").
		SetInt64("total_fee", 1).
		SetString("out_trade_no", tradeNum).
		SetString("spbill_create_ip", common.Config.WX.SpbillCreateIP).
		SetString("notify_url", common.Config.WX.NotifyUrl).
		SetString("trade_type", "JSAPI")
	data, err := client.UnifiedOrder(params)
	if err != nil {
		logger.Error("下单发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "下单发生错误" + err.Error(), 1})
		return
	}
	fmt.Println(data)

	// 订单查询
	//params = make(wxpay.Params)
	//params.SetString("out_trade_no", "3568785")
	//data, err = client.OrderQuery(params)
	// 微信支付 结束-----------------------------------------------------------------

	_ = conn.Commit()

	sql = "select * from student_info where id=?"
	model = &models.Info{}
	err = conn.Raw(sql, userId).QueryRow(model)
	if err != nil {
		logger.Error("查询用户信息发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "查询用户信息发生错误" + err.Error(), 1})
		return
	}

	result := map[string]interface{}{}
	result["status"] = 0
	result["success"] = true
	result["msg"] = "用户交费成功"
	result["user"] = model
	web.ServeJSON(w, result)
}

// @Title 上传用户相关图片
// @Route /user/upload --> Upload
// @Param {"userId":0, "token":"", "b64":"base64Code", "b64sm":"base64Code", "imgType":"jpg", "fieldName":"picture"}
// @Method post
func (this *UserController) Upload(w http.ResponseWriter, r *http.Request) {
	defer web.PanicHandler(w, r)
	inputJson, msg := web.TokenCheckRequest(r)
	if msg != nil {
		web.ServeJSON(w, msg)
		return
	}

	userId := inputJson["userId"]
	b64 := inputJson["b64"]
	b64sm := inputJson["b64sm"]
	imgType := inputJson["imgType"]
	fieldName := inputJson["fieldName"]

	if b64 == nil || b64 == "" {
		web.ServeJSON(w, web.JsonMsg{false, "图片数据不能为空", 1})
		return
	}
	if b64sm == nil || b64sm == "" {
		web.ServeJSON(w, web.JsonMsg{false, "缩略图数据不能为空", 1})
		return
	}
	if imgType == nil {
		web.ServeJSON(w, web.JsonMsg{false, "图片扩展名不能为空", 1})
		return
	}
	if fieldName == nil {
		web.ServeJSON(w, web.JsonMsg{false, "图片字段名不能为空", 1})
		return
	}

	imgStr, err := base64.StdEncoding.DecodeString(b64.(string))
	if err != nil {
		logger.Error("图片数据不正确", err)
		web.ServeJSON(w, web.JsonMsg{false, "图片数据不正确" + err.Error(), 1})
		return
	}

	// 按"年/月/日"创建文件夹, 防止一个文件夹内文件过多造成读取缓慢
	filePath := "student/" + common.GetNowWithFormat("2006") + "/"
	filePath += common.GetNowWithFormat("01") + "/"
	filePath += common.GetNowWithFormat("02") + "/"
	fileName := common.ToStr(userId) + "_" + common.ToStr(fieldName)
	fileUrl := filePath + fileName + "." + common.ToStr(imgType)

	// 绝对路径(防止"MkdirAll"将文件夹创建在"C:\Windows\System32")
	absPath := common.Config.UploadPath
	err = common.CreateChildDir(absPath + filePath)
	if err != nil {
		logger.Error("创建文件夹发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "创建文件夹发生错误" + err.Error(), 1})
		return
	}

	file, err := os.Create(absPath + fileUrl)
	if err != nil {
		logger.Error("创建图片文件发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "创建图片文件发生错误" + err.Error(), 1})
		return
	}
	_, err = io.WriteString(file, string(imgStr))
	if err != nil {
		logger.Error("保存图片文件发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "保存图片文件发生错误" + err.Error(), 1})
		return
	}

	// 缩略图上传
	imgStr, err = base64.StdEncoding.DecodeString(b64sm.(string))
	if err != nil {
		logger.Error("缩略图格式不正确", err)
		web.ServeJSON(w, web.JsonMsg{false, "缩略图格式不正确" + err.Error(), 1})
		return
	}

	fileUrl = filePath + fileName + "_sm." + common.ToStr(imgType)
	file, err = os.Create(absPath + fileUrl)
	if err != nil {
		logger.Error("创建缩略图文件发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "创建缩略图文件发生错误" + err.Error(), 1})
		return
	}
	_, err = io.WriteString(file, string(imgStr))
	_ = file.Close()
	if err != nil {
		logger.Error("保存缩略图文件发生错误", err)
		web.ServeJSON(w, web.JsonMsg{false, "保存缩略图文件发生错误" + err.Error(), 1})
		return
	}

	result := map[string]interface{}{}
	result["status"] = 0
	result["success"] = true
	result["msg"] = "图片上传成功"
	result["imageUrl"] = fileUrl
	web.ServeJSON(w, result)
}
